package secret.santa.server;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Stack;

import javax.jdo.PersistenceManager;
import javax.jdo.Query;

import secret.santa.client.CS_Event;
import secret.santa.client.CS_EventEntry;
import secret.santa.client.CS_User;
import secret.santa.client.EventQuery;
import secret.santa.client.SantaService;
import secret.santa.client.SecretData;

import com.google.gwt.user.server.rpc.RemoteServiceServlet;

/**
 * The server side implementation of the RPC service.
 */
@SuppressWarnings("serial")
public class SantaServiceImpl extends RemoteServiceServlet implements
	SantaService 
	{
	
	private final Long TTL = 604800000L; // 1 week
	
	
	

	@SuppressWarnings("unchecked")
	@Override
	public Long login(String username, String password)
	{
		PersistenceManager pm = PMF.get().getPersistenceManager();
		
		Query query = pm.newQuery(DB_User.class);
		
		//TODO: parse username and password for sql injection safety
		
		query.setFilter("username == thisUsername && password == thisPassword");
		query.declareParameters("String thisUsername, String thisPassword");
		
		List<DB_User> ls = (List<DB_User>) query.execute(username, password);
		
		if(ls.size() == 0)
			return 0L;
		DB_User u = ls.get(0);
		long curTime = System.currentTimeMillis();
		Random r = new Random(curTime);
		Long sid = r.nextLong();
		
		
		//first remove all dead sesions for this user.
		Query q = pm.newQuery(DB_Session.class);
		q.setFilter("userKey == thisUsername");
		q.declareParameters("String thisUsername");
		q.deletePersistentAll(username);
		
		DB_Session s = new DB_Session();
		s.setExpires(curTime + TTL);
		s.setSid(sid);
		s.setUserKey(u.getEncodedKey());
		
		try 
		{
			pm.makePersistent(s);
		}
	    finally
	    { 
	            pm.close(); 
	    }
		return sid;
	}
	public Boolean login(long sid)
	{
		PersistenceManager pm = PMF.get().getPersistenceManager();
		
		DB_Session s = pm.getObjectById(DB_Session.class, sid);
		
		if(s == null)
			return false;
		
		long curDate = System.currentTimeMillis();
		
		if(curDate < s.getExpires())
		{
			pm.deletePersistent(s);
		}
		pm.close();
		
		return true;
	}
	private boolean isAdmin(Long sid)
	{
		
		PersistenceManager pm = PMF.get().getPersistenceManager();
		DB_Session session = pm.getObjectById(DB_Session.class, sid);
		
		if(session == null)
			return false;
		
		
		DB_User u = pm.getObjectById(DB_User.class, session.getUserKey());
		
		if(u.getUsername().equals("admin"))
			return true;
		
		return false;		
	}

	@SuppressWarnings("unchecked")
	public CS_Event[] getEvents(Long sid)
	{
		if(!isAdmin(sid))
			return null;
		
		PersistenceManager pm = PMF.get().getPersistenceManager();
		
		Query query = pm.newQuery(DB_Event.class);
		
		List<DB_Event> l = (List<DB_Event>) query.execute();
		
		DB_Event[] a = l.toArray(new DB_Event[l.size()]);
		
		int size = a.length;
		
		CS_Event[] b = new CS_Event[size];
		for(int i = 0; i < size; i++)
			b[i] = a[i].toClient();
		
		pm.close();
		return b;
	}
	public Boolean addEvent(Long sid, String name, String description )
	{
		if(!isAdmin(sid))
			return false;
		
		PersistenceManager pm = PMF.get().getPersistenceManager();
		
		DB_Event e = new DB_Event();
		e.setName(name);
		e.setDescription(description);
		
		pm.makePersistent(e);
		
		pm.close();
		
		return true;
	}
	public Boolean addUser(Long sid, String login, String password, String fullName, String email )
	{
		if(!isAdmin(sid))
			return false;
		DB_User u = new DB_User();
		PersistenceManager pm = PMF.get().getPersistenceManager();
		
		u.setUsername(login);
		u.setPassword(password);
		
		u.setFullName(fullName);
		u.setEmail(email);
		
		pm.makePersistent(u);
		
		
		pm.close();
		
		return true;
	}
	@Override
	public Boolean makeCouple(Long sid, String key1, String key2)
	{
		if(!isAdmin(sid))
			return false;
		
		PersistenceManager pm = PMF.get().getPersistenceManager();
		
		DB_User user1 = pm.getObjectById(DB_User.class, key1);
		DB_User user2 = pm.getObjectById(DB_User.class, key2);
		
		user1.setPartner(user2.getEncodedKey());
		user2.setPartner(user1.getEncodedKey());
		
		pm.close();
		
		return true;
	}
	@SuppressWarnings("unchecked")
	@Override
	public CS_User[] getUsers(Long sid) 
	{
		if(!isAdmin(sid))
			return null;
		
		PersistenceManager pm = PMF.get().getPersistenceManager();
		
		Query query = pm.newQuery(DB_User.class);
		
		List<DB_User> l = (List<DB_User>) query.execute();
		int size = l.size();
		DB_User[] a = new DB_User[size];
		CS_User[] b = new CS_User[size];
		a = l.toArray(a);
		
		for(int i = 0; i < size; i++)
			b[i] = a[i].toClient();
		pm.close();
		return b;
	}
	@Override
	public Boolean editEvent(Long sid, String key, String name, String desc) 
	{
		if(!isAdmin(sid))
			return null;
		PersistenceManager pm = PMF.get().getPersistenceManager();
		DB_Event e = pm.getObjectById(DB_Event.class, key);
		
		e.setName(name);
		e.setDescription(desc);
		
		pm.close();
		
		return true;
	}
	@Override
	public Boolean editUser(Long sid, String key, String username, String password,
			String fullName, String email)
	{
		if(!isAdmin(sid))
			return null;
		PersistenceManager pm = PMF.get().getPersistenceManager();
		
		DB_User u = pm.getObjectById(DB_User.class, key);
		
		u.setUsername(username);
		u.setPassword(password);
		u.setFullName(fullName);
		u.setEmail(email);

		

		pm.close();
		// TODO Auto-generated method stub
		return true;
	}
	@Override
	public Boolean removeEvent(Long sid, String key) 
	{
		if(!isAdmin(sid))
			return null;
		
		PersistenceManager pm = PMF.get().getPersistenceManager();
		DB_Event e = pm.getObjectById(DB_Event.class, key);
		
		pm.deletePersistent(e);
		
		pm.close();
		return true;
	}
	@Override
	public Boolean removeUsers(Long sid, String [] keys) 
	{
		if(!isAdmin(sid))
			return null;
		PersistenceManager pm = PMF.get().getPersistenceManager();
		int size = keys.length;
		for(int i = 0; i < size; i++)
		{
			DB_User u = pm.getObjectById(DB_User.class, keys[i]);
			pm.deletePersistent(u);
		}
		
		pm.close();
		return true;
	}
	@Override
	public Boolean removeUser(Long sid, String key) 
	{
		if(!isAdmin(sid))
			return null;
		
		PersistenceManager pm = PMF.get().getPersistenceManager();
		DB_User u = pm.getObjectById(DB_User.class, key);
		
		pm.deletePersistent(u);
		
		pm.close();
		return true;
	}
	@SuppressWarnings("unchecked")
	@Override
	public EventQuery queryEvent(Long sid, String key) 
	{
		if(!isAdmin(sid))
			return null;
		
		//TODO: efficiency
		PersistenceManager pm = PMF.get().getPersistenceManager();
		DB_Event e = pm.getObjectById(DB_Event.class, key);
		
		Query query = pm.newQuery(DB_EventEntry.class);
		
		query.setFilter("eventKey == thisKey");
		query.declareParameters("String thisKey");
		
		
		List<DB_EventEntry> entries = (List<DB_EventEntry>)query.execute(key);
		ArrayList<String> parts = e.getParticipants();
		EventQuery ret = new EventQuery();
		int size = parts.size();
		ret.users = new CS_User[size];
		//TODO: again ohmygawd performance maybe a query?
		for(int i = 0; i< size; i++)
		{
			DB_User u= (DB_User)pm.getObjectById(DB_User.class,  parts.get(i));
			ret.users[i] = u.toClient();
		}
		
		size = entries.size();
		ret.entries = new CS_EventEntry[size];
		for(int i = 0; i< size; i++)
		{
			ret.entries[i] = entries.get(i).toClient();
		}
		
		
		return ret;
	}
	@Override
	public Boolean addUsersToEvent(Long sid, String[] userKeys, String eventKey) 
	{
		if(!isAdmin(sid))
			return null;
		PersistenceManager pm = PMF.get().getPersistenceManager();
		DB_Event e = pm.getObjectById(DB_Event.class, eventKey);
		
		ArrayList<String> parts = e.getParticipants();
		
		
		int size = userKeys.length;
		for(int i = 0; i < size; i++)
		{//TODO: performance
			parts.remove(userKeys[i]);
			parts.add(userKeys[i]);
		}
		
		pm.close();
				
		return true;
	}
	@Override
	public Boolean generateList(Long sid, String eventKey)
	{
		if(!isAdmin(sid))
			return null;
		
		PersistenceManager pm = PMF.get().getPersistenceManager();
		DB_Event e = pm.getObjectById(DB_Event.class, eventKey);
		
		ArrayList<String> keys = e.getParticipants();
		
		int size = keys.size();
		if(size < 4)
			return false;
		
		
		ArrayList<DB_User> from = new ArrayList<DB_User>();
		ArrayList<DB_User> to = new ArrayList<DB_User>();
		Stack<DB_EventEntry> stack = new Stack<DB_EventEntry>();

		for(int i = 0; i < size; i++)
		{
			DB_User u= (DB_User)pm.getObjectById(DB_User.class,  keys.get(i));
			
			from.add(u);
			to.add(u);
		}
		Random rand = new Random();
		for(int i = 0; i < size; i++)
		{
			DB_User f = from.get(i);
			String fromKey = f.getEncodedKey();
			String partnerKey = f.getPartner();
			if(to.size() > 4)
			{
				int r = rand.nextInt(to.size());
				DB_User t = to.get(r);
				while(t == f || t.getEncodedKey().equals(partnerKey))
				{
					r = rand.nextInt(to.size());
					t = to.get(r);
				}
				
				stack.push(makeEventEntry(eventKey, fromKey, t.getEncodedKey()));
				
				to.remove(r);
				
			}
			/*
			 * there's always a solution for 4 entries, not always for 3
			 * So we hardcode a solution into it and we mix it up so people don't give gifts
			 * to each other all the time;
			 */
			else 
			{
				int j = 0;
				DB_User t = to.get(j);
				
				while(t != null && (t == f || t.getEncodedKey().equals(partnerKey)))
				{
					j++;
					if(j < to.size())
						t = to.get(j);
					else
					{
						t = null;
					}
				}
				if(t!= null)
					stack.push(makeEventEntry(eventKey, fromKey, t.getEncodedKey()));
				else
				{
					/*
					 *  the only way we can get here is if the last one or two entries can't be coupled.
					 * this means, given a couple A B the only possible way we can get here is
					 * from {A B } to {A B} or from {A} to {A} or from {A} to {B}
					 * 
					 * So if we pop the last 2 entries from the stack and replace the To side with one of
					 * these, it'll work
					 */
					DB_EventEntry e1 = stack.pop();
					DB_EventEntry e2 = stack.pop();
					
					// if e1.from is not the user itself or the partner add it;
					if(!fromKey.equals( e1.getKeyTo()) && partnerKey.equals(e1.getKeyTo()))
					{
						stack.push(makeEventEntry(eventKey, fromKey, e1.getKeyTo()) );
						stack.push(e2);
						j--;
						e1.setKeyTo(to.get(j).getEncodedKey());
						stack.push(e1);
					}
					else
					{
						stack.push(makeEventEntry(eventKey, fromKey, e2.getKeyTo()) );
						stack.push(e1);
						j--;
						e2.setKeyTo(to.get(j).getEncodedKey());
						stack.push(e2);
					}
				}
				
				to.remove(j);
			}
			
		}
		
		//remove any previous entries from the database.
		Query q = pm.newQuery(DB_EventEntry.class);
		q.setFilter("eventKey == someKey");
		q.declareParameters("String someKey");
		q.deletePersistentAll(eventKey);
		// and save the new values
		pm.makePersistentAll(stack);
		
		pm.close();
		
		
		return true;
		
		
	}
	private DB_EventEntry makeEventEntry(String eventKey, String fromKey, String toKey) 
	{
		DB_EventEntry ret = new DB_EventEntry();
		
		ret.setEventKey(eventKey);
		ret.setKeyFrom(fromKey);
		ret.setKeyTo(toKey);
		return ret;
		
	}
	@SuppressWarnings("unchecked")
	@Override
	public SecretData getSecretData(Long sid)
	{
		PersistenceManager pm = PMF.get().getPersistenceManager();
		
		DB_Session s = pm.getObjectById(DB_Session.class, sid);
		
		if(s == null)
			return null;
		
		DB_User u = pm.getObjectById(DB_User.class, s.getUserKey());
		
		Query q = pm.newQuery(DB_EventEntry.class);
		q.setFilter("keyFrom == someKey");
		q.declareParameters("String someKey");
		
		List<DB_EventEntry> lfrom = (List<DB_EventEntry>) q.execute(s.getUserKey());
		
		q.setFilter("keyTo == someKey && eventKey == someOtherKey");
		q.declareParameters("String someKey, String someOtherKey");
		
		
		int size = lfrom.size();
		SecretData ret = new SecretData();
		ret.init(size);
		
		ret.name = u.getFullName();
		for(int i = 0; i < size; i++ )
		{
			DB_EventEntry ee = lfrom.get(i);
			DB_Event e= pm.getObjectById(DB_Event.class, ee.getEventKey());
			DB_User santa = pm.getObjectById(DB_User.class, ee.getKeyTo());
			ret.names[i] = santa.getFullName();
			ret.eventDescriptions[i] = e.getDescription();
			ret.event[i] = e.getName();
			ret.santaWishlist[i] = ee.getWishList();
			
			List<DB_EventEntry> lto = (List<DB_EventEntry>) q.execute(s.getUserKey(), e.getEncodedKey());
			
			ee = lto.get(0);
			ret.eventEntryKeys[i] = ee.getEncodedKey();
			ret.yourWishlist[i] = ee.getWishList();
			
		}
		pm.close();
		return ret;
		
	}
	@Override
	public Boolean editWishList(Long sid, String eventEntryKey, String wishList)
	{
		PersistenceManager pm = PMF.get().getPersistenceManager();
		
		DB_Session s = pm.getObjectById(DB_Session.class, sid);
		
		if(s == null)
			return false;
		
		DB_EventEntry ee = pm.getObjectById(DB_EventEntry.class, eventEntryKey);
		
		if(!s.getUserKey().equals(ee.getKeyTo()))
				return false;
		
		ee.setWishList(wishList);
		
		pm.close();
		return true;
		
	}
}